﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using BrainTechLLC;

namespace DragDropLibrary
{
    public partial class DragAndDrop
    {
        public static FastSmartWeakEvent<EventHandler<DropEventArgs<object>>> _dropCompleted;
        public static event EventHandler<ObjectEventArgs<ItemsControl, object>> DropSuccessful;

        public static event EventHandler<DropEventArgs<object>> DropCompleted
        {
            add
            {
                if (_dropCompleted == null)
                {
                    Interlocked.CompareExchange<FastSmartWeakEvent<EventHandler<DropEventArgs<object>>>>(ref _dropCompleted, new FastSmartWeakEvent<EventHandler<DropEventArgs<object>>>(), null);
                }

                _dropCompleted.Add(value);
            }
            remove { _dropCompleted.Remove(value); }
        }

        #region DropEnabled

        /// <summary>
        /// DropEnabled is an attached property that can be used to enable dropping data items from the drag source items control to the specified UIElement
        /// DropEnabled can be set as an attached property on any UI Element
        /// </summary>
        public static readonly DependencyProperty DropEnabledProperty =
            DependencyProperty.RegisterAttached("DropEnabled", typeof(Boolean), typeof(DragAndDrop), new FrameworkPropertyMetadata(OnDropEnabledChanged));

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetDropEnabled(DependencyObject element, Boolean value)
        {
            element.SetValue(DropEnabledProperty, value);
        }

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static Boolean GetDropEnabled(DependencyObject element)
        {
            return (Boolean)element.GetValue(DropEnabledProperty);
        }

        /// <summary>
        /// This method is called whenever the attached property "DropEnabled" is changed or set
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="args"></param>
        public static void OnDropEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var ctrl = obj as UIElement;

            if (args.NewValue != null && !string.IsNullOrEmpty(args.NewValue.ToString()))
            {
                if ((bool)args.NewValue == true)
                {
                    ctrl.AllowDrop = true;
                    ctrl.Drop += new DragEventHandler(ctrl_Drop);
                }
                else
                {
                    ctrl.AllowDrop = false;
                    ctrl.Drop -= ctrl_Drop;
                }
            }
        }

        #endregion

        public static readonly DependencyProperty RegisterDropTargetNameProperty =
            DependencyProperty.RegisterAttached("RegisterDropTargetName", typeof(string), typeof(DragAndDrop), new FrameworkPropertyMetadata(string.Empty, OnRegisterDropTargetNameChanged));

        public static void SetRegisterDropTargetName(DependencyObject element, string value)
        {
            element.SetValue(RegisterDropTargetNameProperty, value);
        }

        public static string GetRegisterDropTargetName(DependencyObject element)
        {
            return (string)element.GetValue(RegisterDropTargetNameProperty);
        }

        public static void OnRegisterDropTargetNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null && !string.IsNullOrEmpty(args.NewValue.ToString()))
            {
                SetDropEnabled(obj, true);
            }
        }

        public static readonly DependencyProperty DropMethodNameProperty =
            DependencyProperty.RegisterAttached("DropMethodName", typeof(string), typeof(DragAndDrop), new FrameworkPropertyMetadata(string.Empty, OnDropMethodNameChanged));

        public static void SetDropMethodName(DependencyObject element, string value)
        {
            element.SetValue(DropMethodNameProperty, value);
        }

        public static string GetDropMethodName(DependencyObject element)
        {
            return (string)element.GetValue(DropMethodNameProperty);
        }

        public static void OnDropMethodNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null && !string.IsNullOrEmpty(args.NewValue.ToString()))
            {
                SetDropEnabled(obj, true);
            }
        }

        private static MethodInfo CrawlAndLookForMethod(FrameworkElement element, string methodName, out object found)
        {
            if (element == null)
            {
                found = null;
                return null;
            }

            var methodInfo = element.GetType().GetMethod(methodName);

            if (methodInfo != null)
            {
                found = element;
                return methodInfo;
            }

            object parent = VisualTreeHelper.GetParent(element);

            if (parent != null)
            {
                return CrawlAndLookForMethod(parent as FrameworkElement, methodName, out found);
            }

            found = null;
            return null;
        }

        /// <summary>
        /// Fired when a drop operation occurs
        /// </summary>
        /// <param name="sender">sender is the control to which the data was dropped</param>
        /// <param name="e"></param>
        private static void ctrl_Drop(object sender, DragEventArgs e)
        {
            string dropMethodName = GetDropMethodName(sender as UIElement);

            object foundObject = null;

            // If a drop method name is specified, crawl and look for the method name
            MethodInfo mi = string.IsNullOrEmpty(dropMethodName) ? null : CrawlAndLookForMethod(sender as FrameworkElement, dropMethodName, out foundObject);

			// Mark that we are no longer dragging
            IsDragging = false;
            
            // Get the dragged object data
            object data = e.Data.GetData(DragType);
            
            // determine where the drop operation occurred relative to the control to which the data was dropped
            Point pt = e.GetPosition(sender as UIElement);

            //Change: Check if type is visible, remove only visible items from DragSource...
            if (DragType.IsVisible == true)
            {
                DropEventArgs<object> args = new DropEventArgs<object>(data, DragSource, sender as UIElement, pt);

                // if a drop method name is specified and the method was found, execute the method
                if (mi != null)
                {
                    mi.Invoke(foundObject, new object[1] { args });
                }

                if (_dropCompleted != null)
                {
                    _dropCompleted.Raise(sender, args);
                }

                // new code
                // Owen's fancy stuff to hook up Drop targets that accept a certain type of object with the actual data object type
                Type genericType = typeof(IDropTarget<>);

                // Create a generic type IDropTarget<T> where T is the actual type of data being dropped
                Type actualType = genericType.MakeGenericType(DragType);

                // get the type onto which the data was dropped
                Type senderType = sender.GetType();
                Type foundInterface;

                // Find an interface IDropTarget<> corresponding to the type of data dropped
                foundInterface = senderType.GetInterfaces().FirstOrDefault((t) => t == actualType);

                // Create the event args.  More fun reflection and dynamic type creation stuff
                Type eventArgsType = typeof(DropEventArgs<>);
                Type actualEventArgsType = eventArgsType.MakeGenericType(DragType);

                // Create a generic constructor based on the type of dropped data
                ConstructorInfo eventConstructor = actualEventArgsType.GetConstructor(new Type[] { DragType, typeof(ItemsControl), typeof(UIElement), typeof(Point) });

                Debug.Assert(eventConstructor != null, "Someone changed the constructor definition for generic type DropEventArgs<T>... uh oh... crash and burn!");

				// Create a strongly-typed DropEventArgs<T> where T is the actual type of data dropped
                object arguments = eventConstructor.Invoke(new object[] { data, DragSource, sender as UIElement, pt });

                // Get the interface method "HandleDroppedData"
                MethodInfo methodToCall = senderType.GetMethod("HandleDroppedData", new Type[] { actualEventArgsType });

				// Call the method with the appropriate strongly-typed event args
                if (methodToCall != null)
                {
                    methodToCall.Invoke(sender, new object[] { arguments });
                }

                IDragSource source = null;
                UIElement element = DragSource;

				// If the drag source or its containing class implements IDragSource, then
                // give the drag source notification that the drop was completed
                while (element != null)
                {
                    source = element as IDragSource;

                    if (source != null)
                    {
                        break;
                    }

                    element = VisualTreeHelper.GetParent(element) as UIElement;
                }

                if (source != null)
                {
  					// Notify the drag source that the drop was completed
                    source.DropComplete(sender, data, DragSource);
                }
            }

            DragSourceName = string.Empty;
        }
    }
}
